/***************************************************************************
 *   This is a part of the NextCAD Web APP.                                *
 *   Copyright (C) AMCAX, All rights reserved.                             *
 ***************************************************************************/

// 草图 - 样条曲线

import { KEYBOARD_KEY } from '@app-cad/common/Constants';
import { wsApi } from '@app-cad/server/wsApi';
import i18n from 'i18next';
import { useEffect, useState } from 'react';
import { CatmullRomCurve3, Object3D, Scene, Vector3 } from 'three-legacy';

import {
  BaseCommand,
  CENTEREVENTS,
  CSLine,
  CSPoint,
  CSys,
  CadScene,
  CommandHint,
  CommandHintStatus,
  EventsRes,
  MathTool,
  SceneGraph,
  css2dInputLable,
  useStore,
} from '@amcax/base';
import { ctx2 } from '@app-cad/module';

import { Snaper } from '@amcax/renderer';
import { eventCenterManager } from '@app-base/common/events/EventCenterManager';

let isMove = false;

enum State {
  NONE,
  DRAW,
  FINISH,
  EDIT,
}

type MouseState = State.NONE | State.DRAW | State.FINISH | State.EDIT;

interface TrackingData {
  line: CSLine;
  // line: Line,
  label: css2dInputLable;
}

interface LineCommandProps {
  title: string;
  onClose: () => void;
}

function LineCommand({ onClose }: LineCommandProps) {
  const updateCmdHighlightId = useStore((state) => state.updateCmdHighlightId);
  const [hits, setHits] = useState<string>('');
  const cadScene = ctx2.cadScene as CadScene;
  const sceneSetup = ctx2.viewer.sceneSetup;
  const assisGroup = cadScene.toolsGroup;
  const lineScene = new Scene();
  const xAxis = new Vector3(1, 0, 0);
  const yAxis = new Vector3(0, 1, 0);
  const zAxis = new Vector3(0, 0, 1);

  // 默认为 XOY 平面
  let faceCSys: CSys = MathTool.createCsysFromPlacement([
    [0, 0, 0],
    [0, 0, 1],
    [1, 0, 0],
  ]);
  let planeNormal: Vector3 = faceCSys.getNormalVc3();
  let planeOrigin: Vector3 = faceCSys.getOriginVc3();
  let planeName: string = 'XOY';
  let currentPlaneName: string = '';

  let startPoint: Vector3; // 绘制起点
  let endPoint: Vector3; // 绘制终点
  const points: Vector3[] = [];

  const assistPoints: CSPoint[] = [];

  let currentStatus: MouseState = State.NONE; // 记录当前状态

  const trackingData: TrackingData = {
    line: null,
    label: null,
  };

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  let pickedCurve = null;
  let nowTakenPoint = null;

  useEffect(() => {
    init();
    return () => {
      onDestroy();
    };
  }, []);

  const init = () => {
    assisGroup.add(lineScene);
    registerEvents();
    setHits(
      '点击确定顶点，当前绘制平面为 ' +
        planeName +
        ' 平面，按方向键切换绘制平面',
    );
  };
  const onDestroy = () => {
    eventCenterManager.activeEventCenter
      .setDefault()
      .off(CENTEREVENTS.POINTERUP, onPointerUp)
      .off(CENTEREVENTS.POINTERMOVE, onPointerMove)
      .off(CENTEREVENTS.KEYUP, onKeyup);
    releaseObjects();
    onClose();
    setHits('');
    // ctx2.off('ExitBspline')
    pickedCurve = null;
  };

  // ========= 鼠标和键盘事件=========
  const registerEvents = () => {
    eventCenterManager.activeEventCenter
      .setState('spline')
      .on(CENTEREVENTS.POINTERUP, onPointerUp)
      .on(CENTEREVENTS.POINTERMOVE, onPointerMove)
      .on(CENTEREVENTS.KEYUP, onKeyup)
      .addExit(onDestroy);
  };

  const onKeyup = (e: EventsRes<KeyboardEvent>) => {
    if (currentStatus === State.EDIT) {
      return;
    }

    if (e.key === KEYBOARD_KEY.ESC) {
      onDestroy();
    } else if (e.key === KEYBOARD_KEY.ENTER) {
      if (currentStatus === State.DRAW) {
        finishDraw();
        onDestroy();
      }
    } else if (e.key === KEYBOARD_KEY.ARROWUP) {
      changePlaneToXOY();
    } else if (e.key === KEYBOARD_KEY.ARROWRIGHT) {
      changePlaneToYOZ();
    } else if (e.key === KEYBOARD_KEY.ARROWLEFT) {
      changePlaneToXOZ();
    } else if (e.key === KEYBOARD_KEY.ARROWDOWN) {
      changePlaneToXOY();
    }
  };
  const onPointerUp = (e: any) => {
    if (currentStatus !== State.EDIT) {
      let needUpdata = false;
      if (e.button.onLeft) {
        // currentStatus = State.DRAW
        trackingData.line = null;
        // 鼠标左键，开始画线，记录起始点，并添加到points中
        if (points.length > 0) {
          if (isMove) {
            // 有新增的点
            // isMove鼠标不移动即点没有发生移动 故不执行下面逻辑
            // 如果已经画了线段，下个线段的起点设置为上个线段的终点
            startPoint = nowTakenPoint
              ? nowTakenPoint
              : points[points.length - 1];
            planeOrigin = startPoint.clone();
            points.push(startPoint);
            isMove = false;
          }
          // 更新曲线 不管有没有新增的点都需要更新曲线
          needUpdata = true;
        } else {
          startPoint = nowTakenPoint
            ? nowTakenPoint
            : sceneSetup.mouseOnPlane(
                e.mouse.x,
                e.mouse.y,
                planeNormal,
                planeOrigin,
              );
          planeOrigin = startPoint.clone();
          points.push(startPoint);
          // changePlaneToXOY()
        }
        // 添加辅助点，用来做定点捕捉
        createAssistPoint(startPoint);

        // 更新曲线
        if (needUpdata) {
          lineScene.clear();
          const curveX = new CatmullRomCurve3(points);
          const vertices = curveX.getPoints(50 * points.length);
          trackingData.line = new CSLine({
            vertexs: vertices,
            dashed: false,
          });
          lineScene.add(trackingData.line);
        }

        currentStatus = State.DRAW;
      } else if (e.button.onRight) {
        if (
          points.length > 1 &&
          points[points.length - 1].equals(points[points.length - 2])
        ) {
          points.pop();
        }
        currentStatus = State.FINISH;
        // 鼠标右键，完成
        finishDraw();
        onDestroy();
      }
    }
  };

  const onPointerMove = (e: EventsRes) => {
    if (e.button.buttonDown) {
      return;
    }
    let pickRes;
    // const pickRes = Picker.pick(e, { face: true, line: true })
    if (currentStatus === State.EDIT) {
      pickRes = null;
    } else {
      // pickRes = Picker.pick(e, { face: true, line: true })
      pickRes = null;
    }

    // return
    nowTakenPoint = null;
    if (pickRes && currentStatus !== State.EDIT) {
      const picked = Snaper.snapKeyPoint(e, {
        lineCross: false,
        faceCross: false,
      });
      nowTakenPoint = picked?.vector;
    }

    if (currentStatus === State.NONE) {
      if (pickRes && pickRes?.assisObj) {
        if (pickRes.assisObj.name === 'pickedFace' && pickRes.idInfo.t !== 3) {
          const csys = pickRes.assisObj.csys;
          const raycaster = sceneSetup.createRaycaster(e.mouse.x, e.mouse.y);
          const results = raycaster.intersectObjects(assisGroup.children, true);
          planeOrigin = results[0]?.point;
          planeNormal = csys.getNormalVc3().clone();
          planeName = '拾取';
          if (planeName !== currentPlaneName) {
            setHits('点击确定顶点，当前绘制平面为 ' + planeName + ' 平面');
            currentPlaneName = planeName;
          }
          pickedCurve = null;
        } else if (pickRes.assisObj.name === 'pickCurve') {
          pickedCurve = pickRes;
        } else {
          // assisGroup.remove(pickRes.assisObj)
          pickedCurve = null;
        }
      } else {
        if (planeName === '拾取') {
          planeOrigin = new Vector3(0, 0, 0);
          planeNormal = new Vector3(0, 0, 1);
          planeName = 'XOY';
          if (planeName !== currentPlaneName) {
            setHits(
              '点击确定顶点，当前绘制平面为 ' +
                planeName +
                ' 平面，按方向键切换绘制平面',
            );
            currentPlaneName = planeName;
          }
        }
      }
    }

    if (currentStatus === State.DRAW) {
      let hasPoint = false; // 是否有可吸附的点

      if (trackingData.line) {
        // let results = ctx2.viewer.raycast(e.mouseEvent, assistPoints, RayCastDebugInfo)
        // let results = sceneSetup.raycast(e.mouse, assistPoints)
        const raycaster = sceneSetup.createRaycaster(e.mouse.x, e.mouse.y);
        let results = raycaster.intersectObjects(assistPoints, true);
        // 过滤掉当前点，对当前点不做吸附判断
        results = results.filter((result) => {
          const ret = isLastPoint(result.object);
          return !ret;
        });
        if (results.length > 0) {
          const targetPoint = results[0].object as CSPoint;
          endPoint = targetPoint.getVertex();
          hasPoint = true;
        }
      }
      if (!hasPoint) {
        endPoint = nowTakenPoint
          ? nowTakenPoint
          : sceneSetup.mouseOnPlane(
              e.mouse.x,
              e.mouse.y,
              planeNormal,
              planeOrigin,
            );
        isMove = true;
      } else {
        if (endPoint.equals(points[points.length - 2])) {
          // 如果有吸附点 且吸附点和上一个点位置相同 即等于鼠标未移动
          isMove = false;
        }
      }

      // 添加辅助点，用来做定点捕捉
      createAssistPoint(endPoint, true);
      if (points.length === 1) {
        points.push(endPoint);
      } else {
        points[points.length - 1] = endPoint;
      }

      preview();
      ctx2.viewer.requestRender();
    }
  };
  const createAssistPoint = (
    point: Vector3,
    is_move: boolean = false,
  ): CSPoint => {
    const assistPoint: CSPoint = new CSPoint({
      vertex: point,
      pointType: 'endPoint',
    });
    assistPoint.hoverOn();
    if (!is_move || assistPoints.length === 1) {
      assistPoints.push(assistPoint);
      assisGroup.add(assistPoint);
    } else {
      assisGroup.remove(assistPoints[assistPoints.length - 1]);
      assisGroup.add(assistPoint);
      assistPoints[assistPoints.length - 1] = assistPoint;
    }
    return assistPoint;
  };
  const isLastPoint = (point: Object3D): boolean => {
    if (points.length > 0) {
      const lastAssistPoint = assistPoints[assistPoints.length - 1];
      return lastAssistPoint.uuid === point.uuid;
    }
    return false;
  };
  // ========= 鼠标和键盘事件 =========
  // ========= 位置变换 =========
  const changePlaneToXOY = () => {
    changePlane(zAxis, 'XOY');
  };
  const changePlaneToYOZ = () => {
    changePlane(xAxis, 'YOZ');
  };
  const changePlaneToXOZ = () => {
    changePlane(yAxis, 'XOZ');
  };

  const changePlane = (_axis: Vector3, _planeName: string) => {
    planeName = _planeName;
    planeNormal = _axis.clone();
    createFaceCSys();
    preview();
    setHits(
      '点击确定顶点，当前绘制平面为 ' +
        planeName +
        ' 平面，按方向键切换绘制平面',
    );
  };

  const createFaceCSys = () => {
    let _xAxis = []; // 局部坐标系的X轴
    let _zAxis = []; // 局部坐标系的Z轴
    if (planeName === 'XOY') {
      _xAxis = [1, 0, 0];
      _zAxis = zAxis.clone().toArray();
    } else if (planeName === 'YOZ') {
      _xAxis = [0, 0, 1];
      _zAxis = xAxis.clone().toArray();
    } else if (planeName === 'XOZ') {
      _xAxis = [1, 0, 0];
      _zAxis = yAxis.clone().toArray();
    }
    const placement = [planeOrigin.clone().toArray(), _zAxis, _xAxis];
    faceCSys = MathTool.createCsysFromPlacement(placement);
    // if (trackingData.line) {
    //   trackingData.line.resetCsys(faceCSys)
    // }
  };

  // ========= 位置变换 =========

  const finishDraw = async () => {
    if (points.length > 0) {
      await wsApi.createBspline(points);
    }
  };

  // 释放对象
  const releaseObjects = () => {
    assisGroup.clear();
    SceneGraph.emptyGroup(assisGroup);
    ctx2.viewer.requestRender();
    updateCmdHighlightId('');
  };

  const preview = () => {
    if (currentStatus === State.DRAW) {
      if (trackingData.line) {
        const curveX = new CatmullRomCurve3(points);
        const vertices = curveX.getPoints(50 * points.length); // 细分曲线数量
        trackingData.line.setVertexs(vertices);
      } else {
        const curveX = new CatmullRomCurve3(points);
        const vertices = curveX.getPoints(50 * points.length);
        trackingData.line = new CSLine({
          vertexs: vertices,
          dashed: false,
        });
        lineScene.add(trackingData.line);
      }
      ctx2.viewer.requestRender();
    }
  };
  return (
    <CommandHint
      status={CommandHintStatus.INFO}
      content={hits}
    />
  );
}

export const cmdSpline: BaseCommand = {
  id: 'CmdSpline',
  label: 'command.spline',
  icon: 'commands/feature/spline',
  create: (label: string, onClose: () => void) => (
    <LineCommand
      title={i18n.t(label)}
      onClose={onClose}
    />
  ),
};
